
import java.awt.Color;
import java.util.ArrayList;

import org.jbox2d.collision.AABB;
import org.jbox2d.collision.Shape;
import org.jbox2d.common.Settings;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.joints.MouseJoint;
import org.jbox2d.dynamics.joints.MouseJointDef;
import org.jbox2d.dynamics.joints.PrismaticJointDef;
import org.jbox2d.dynamics.joints.RevoluteJointDef;

import pulpcore.Input;
import pulpcore.Stage;
import pulpcore.scene.Scene;
import pulpcore.animation.Easing;
import pulpcore.animation.Fixed;
import pulpcore.animation.Timeline;
import pulpcore.animation.Int;
import pulpcore.animation.event.RemoveSpriteEvent;
import pulpcore.image.BlendMode;
import pulpcore.image.CoreFont;
import pulpcore.image.CoreImage;
import pulpcore.math.CoreMath;
import pulpcore.sound.Playback;
import pulpcore.sound.Sound;
import pulpcore.sprite.Group;
import pulpcore.sprite.ImageSprite;
import pulpcore.sprite.Label;
import pulpcore.sprite.Sprite;
import pulpfizz.physics.Actor;
import pulpfizz.physics.BodyUtils;
import pulpfizz.physics.ContactData;
import pulpfizz.physics.ContactEventListener;
import pulpfizz.physics.Material;
import pulpfizz.physics.NewWorld;
import pulpfizz.physics.ragdoll.Biped;
import pulpfizz.pulp.CameraControlledScene;
import pulpfizz.pulp.body.BodySprite;
import pulpfizz.pulp.body.Java2DBodySprite;
import pulpfizz.pulp.body.PhysicsLayer;
import pulpfizz.pulp.ui.SmoothMouse;
import eric.Pulpfire.src.ScoreScene;

public class PulpFireScene extends CameraControlledScene
{
    static final boolean DEBUG = true;
    final static int BULLET_GROUP = 2;
	static final int PLAYER_GROUP = 4;
	static final int BLOB_GROUP = 5;

    static boolean playSounds = true;

    static enum FieldSide{LEFT, RIGHT}

    private SmoothMouse sm;
	private MouseJoint playerMouseJoint;
	private MouseJoint enemyMouseJoint;
	PhysicsLayer physics;
	FireField m_field;
    NewWorld m_world;

    PlayerDoll dollL, dollR;
	Biped bipedL, bipedR;
	Body gunL, gunR;
	Body jetPackL, jetPackR;
	
	Body[] pucks;
	int puckNumber = 5;
	float puckSize = 0.7f;
	
	ArrayList<Body> gravityFreeList;
	
	Sound[] gunSounds;
	Sound[] tickSounds;
	Sound[] thudSounds;
	Sound[] oofSounds;
	Sound slurpSound;
	Sound groanSound, groanRepeatSound;
    Sound losePoint, winPoint;
    Playback groanPlayback;
	
	Sound music;
	
	CoreImage[] images;
    Group particleLayer;
    int lastX, lastY;
    
	private float lMouseTimer = 1.0f;
	private float rMouseTimer = 1.0f;
	private float reloadFrames = 10.0f;
	private float shootSpeed = 50.0f;
	private float leftShotPower = 1.0f;
	private float rightShotPower = 1.0f;
    private float maxShotPower = 3.0f;

    boolean gunUntethered = true;
	
	private int INITIAL_AMMO_PLAYER = 30;
	private int INITIAL_AMMO_OPPONENT = 30;
	
	private boolean playedClickThisFrame = false;
	
	// Proportion of recoil experienced by gun
	float recoilFactor = 0.4f;

	boolean wasMouseInside = false;
	
	float jetPackTargetAngle;
	final float jetPackAngularSpringStrength = 550.0f;
	float jetPackForceLR = 300.0f;
	float jetPackForceU = 500.0f;
	float jetPackForceD = 400.0f;
	
	private float lAngleHipTarget = -2.5f/2.0f;
	private float rAngleHipTarget = -2.5f/2.0f;
	private float lAngleKneeTarget = 1.2f;
	private float rAngleKneeTarget = 1.2f;
	private float kneeConstant = 3.0f;
	private float hipConstant = 3.0f;
	private float angleThreshold = 0.1f;

    ArrayList<Body> tubeR, tubeL;

    ArrayList<Body> puckBits;
    private float puckBitWidth, puckBitHeight;


    Label ammoLLabel, ammoRLabel;
	float ammoLabelDefaultHeight;


    SimulatedInput enemyInput;
    EnemyAI enemyAI;

    private void handleParticles() {
		/*
		if (Input.isMouseInside()) {
            int x = sm.x.getAsInt();//Input.getMouseX();
            int y = sm.y.getAsInt();//Input.getMouseY();
            if (wasMouseInside) {
                int dist = (int)Math.sqrt((lastX - x) * (lastX - x) + (lastY - y) * (lastY - y)); 
                makeParticles(lastX, lastY, x, y, 2 + dist / 8);
            }
            lastX = x;
            lastY = y;
            wasMouseInside = true;
        }
        else {
            wasMouseInside = false;
        }*/
	}
	
	private void makeParticles(int x1, int y1, int x2, int y2, int numParticles, float baseSize) {
        Timeline timeline = new Timeline();
        
        for (int i = 0; i < numParticles; i++) {
            int size = CoreMath.rand((int)(baseSize*.33f), (int)(baseSize*3));
            int duration = (150 - size) * 3;
            int moveDistance = CoreMath.rand(4, 80 - size);
            double moveDirection = CoreMath.rand(0, 2*Math.PI);
            
            int startX = x1 + i * (x2 - x1) / numParticles;
            int startY = y1 + i * (y2 - y1) / numParticles;
            int goalX = startX + (int)(moveDistance * Math.cos(moveDirection));
            int goalY = startY + (int)(moveDistance * Math.sin(moveDirection));
            double startAngle = CoreMath.rand(0, 2*Math.PI);
            double endAngle = startAngle + CoreMath.rand(-2*Math.PI, 2*Math.PI);
            
            CoreImage image = images[CoreMath.rand(images.length - 1)];
            Sprite sprite = new ImageSprite(image, startX, startY);
            sprite.setAnchor(Sprite.CENTER);
            sprite.setSize(size, size);
            particleLayer.add(sprite);
            
            timeline.animateTo(sprite.x, goalX, duration, Easing.REGULAR_OUT);
            timeline.animateTo(sprite.y, goalY, duration, Easing.REGULAR_OUT);
            timeline.animate(sprite.angle, startAngle, endAngle, duration);
            timeline.at(50).animateTo(sprite.alpha, 0, duration - 100, Easing.REGULAR_OUT);
            timeline.add(new RemoveSpriteEvent(particleLayer, sprite, duration));
        }
        
        addTimeline(timeline);

    }

    Label lifeLLabel, lifeRLabel;
    float lifeLabelDefaultHeight;
	
    @Override
	public void load() {
		super.load();

        PuckMaker.listenerNotSet = true;
        Stage.setFrameRate(50);
        camera.zoomTo(1.0f);
		camera.fastForward();
		
		camera.setRespondToInput(false);

        physics = new PhysicsLayer(20.0f, 0, 0, 0, 0);
		physics.fitToStage();

        float pWidth = physics.getPhysicsWidth()*physics.getScaling();
		float pHeight = physics.getPhysicsHeight()*physics.getScaling();
		ImageSprite bg = new ImageSprite("stars.png", 0, 0, pWidth, pHeight);
		add(bg);

		add(physics);
		createWorld();
		
		//CoreFont font = CoreFont.getSystemFont().tint(WHITE);
		CoreFont font = CoreFont.load("complex.font.png");
        lifeLabelDefaultHeight = font.getHeight();
        lifeLLabel = new Label(font, "Points: "+(int)m_field.getLifeL(), 10, 10);
        lifeLLabel.setAnchor(Sprite.NORTH_WEST);
        lifeLLabel.setLocation(10, 10);
        add(lifeLLabel);
        lifeRLabel = new Label(font, "Points: "+(int)m_field.getLifeR(), 10, 10);
        lifeRLabel.setAnchor(Sprite.NORTH_EAST);
        lifeRLabel.setLocation(physics.getViewWidth()-10, 10);
        add(lifeRLabel);

        ammoLabelDefaultHeight = font.getHeight();
        ammoLLabel = new Label(font, "Ammo: "+m_field.getAmmoL(), 10, 10);
        ammoLLabel.setAnchor(Sprite.NORTH_WEST);
        ammoLLabel.setLocation(10, physics.getViewHeight()-10-font.getHeight());
        add(ammoLLabel);
        ammoRLabel = new Label(font, "Ammo: "+m_field.getAmmoR(), 10, 10);
        ammoRLabel.setAnchor(Sprite.NORTH_EAST);
        ammoRLabel.setLocation(physics.getViewWidth()-10, physics.getViewHeight()-10-font.getHeight());
        add(ammoRLabel);

        //thudSounds = new Sound[3];
		//thudSounds[0] = Sound.load("thud.ogg");
		//thudSounds[1] = Sound.load("thud2.ogg");
		//thudSounds[2] = Sound.load("thud3.ogg");
		
		tickSounds = new Sound[1];
		tickSounds[0] = Sound.load("tick.ogg");
		//tickSounds[1] = Sound.load("tick.ogg");
		//tickSounds[2] = Sound.load("tick.ogg");
		//tickSounds[3] = Sound.load("tick.ogg");
		//tickSounds[4] = Sound.load("tick.ogg");
		
		//oofSounds = new Sound[3];
		//oofSounds[0] = Sound.load("oof.ogg");
		//oofSounds[1] = Sound.load("oof2.ogg");
		//oofSounds[2] = Sound.load("oof3.ogg");
		
		gunSounds = new Sound[4];
		gunSounds[0] = Sound.load("gun.ogg");
		gunSounds[1] = Sound.load("gun2.ogg");
		gunSounds[2] = Sound.load("gun3.ogg");
		gunSounds[3] = Sound.load("gun4.ogg");
		
		slurpSound = Sound.load("slurp.ogg");

        //groanSound = Sound.load("groan.ogg");
		//groanRepeatSound = Sound.load("repeatgroan.ogg");

        losePoint = Sound.load("losePoint.ogg");
        winPoint = Sound.load("winPoint.ogg");

        //music = Sound.load("Crossfire.ogg");
		//music.loop(new Fixed(0.3f));
	}

    Vec2 localTubeOffset;

    public void createWorld() {
		NewWorld world = physics.getWorld();
        m_world = world;
        world.setWorldIterations(7);
        physics.setGravity(new Vec2(0.0f,-1.0f)); //low gravity
		gravityFreeList = new ArrayList<Body>();
		Material m;
		Body b;

        enemyInput = new SimulatedInput();
        enemyAI = new EnemyAI(this);

        AABB worldBounds = world.getAABB();
		float worldWidth = worldBounds.upperBound.x - worldBounds.lowerBound.x;
		float worldHeight = worldBounds.upperBound.y - worldBounds.lowerBound.y;
		Vec2 worldCenter = new Vec2(worldBounds.lowerBound.x + worldWidth*.5f,
									worldBounds.lowerBound.y + worldHeight*.5f);
		
		// Set up playing field
		FireField field = new FireField(worldWidth,worldHeight,worldCenter,world);
		field.setPhysicsLayer(physics);
		field.setParent(this);
		field.setAmmoL(INITIAL_AMMO_PLAYER);
		field.setAmmoR(INITIAL_AMMO_OPPONENT);
		m_field = field;
		Body fieldBody = field.getBody();
		Java2DBodySprite.setFill(Color.DARK_GRAY);
		Java2DBodySprite.setStroke(Color.BLACK);
        Java2DBodySprite.setDrawSensors(true);
        Java2DBodySprite.setSensorFill(Color.BLUE);
        Java2DBodySprite.setSensorStroke(Color.BLACK);
        Java2DBodySprite playingField = new Java2DBodySprite(fieldBody,physics);
        Java2DBodySprite.setDrawSensors(false);

        physics.add(playingField);
		Actor fieldActor = (Actor)fieldBody.getUserData();
		fieldActor.addToGroup(1);

		m = Material.WOOD();

		// Create player tether
		tubeL = BodyUtils.createRope(world, field.getLeftEdge(), field.getLeftSemiCenter(), .1f, 12);
		Java2DBodySprite.setFill(Color.BLACK);
		Java2DBodySprite.setStroke(Color.BLACK);
		for (Body body : tubeL) {
			Shape s = body.getShapeList();
			while (s != null) {
				s.m_groupIndex = -10;
				world.refilter(s);
				s = s.getNext();
			}
			m.applyAll(body);
			physics.add(new Java2DBodySprite(body, physics, 2));
		}
		
		// Create enemy tether
		tubeR = BodyUtils.createRope(world, field.getRightEdge(), field.getRightSemiCenter(), .1f, 12);
		Java2DBodySprite.setFill(Color.BLACK);
		Java2DBodySprite.setStroke(Color.BLACK);
		for (Body body : tubeR) {
			Shape s = body.getShapeList();
			while (s != null) {
				s.m_groupIndex = -10;
				world.refilter(s);
				s = s.getNext();
			}
			m.applyAll(body);
			physics.add(new Java2DBodySprite(body, physics, 2));
		}

		// Connect player tether to field
		RevoluteJointDef rjd = new RevoluteJointDef();
		rjd.initialize(field.getBody(),tubeL.get(0),field.getLeftEdge());
        localTubeOffset = rjd.localAnchor2.clone();
        world.createJoint(rjd);
		
		// Connect player tether to field
		RevoluteJointDef rjd2 = new RevoluteJointDef();
		rjd2.initialize(field.getBody(),tubeR.get(0),field.getRightEdge());
		world.createJoint(rjd2);
		
		PlayerDoll playerLeft = new PlayerDoll(this, tubeL, true);
		dollL = playerLeft;
		this.bipedL = playerLeft.biped;
		this.gunL = playerLeft.gun;
		this.jetPackL = playerLeft.jetPack;
		
		PlayerDoll playerRight = new PlayerDoll(this, tubeR, false);
		dollR = playerRight;
		this.bipedR = playerRight.biped;
		this.gunR = playerRight.gun;
		this.jetPackR = playerRight.jetPack;
		
		/*
		 * Mouse stuff.
		 */

		sm = new SmoothMouse();
		add(sm);
		setCursor(Input.CURSOR_HAND);
		
		//Actor bActor = new Actor();
		//bActor.setName("Mouse body");
		//bActor.addToGroup(1);
		//b.setUserData(bActor);
		
		// Give the hands a kick so they grab the gun right
		bipedL.LHand.applyImpulse(new Vec2(-20.0f,0.0f), bipedL.LHand.getWorldCenter());
		bipedL.RHand.applyImpulse(new Vec2(20.0f,0.0f), bipedL.RHand.getWorldCenter());
		
		
		//Bullet sound listener
		ContactEventListener bulletListener = new ContactEventListener() {
			private float forceThreshold = 4.0f;
			private float maxForce = 30.0f;
			public void trigger(ContactData c) {
				if (c.state != ContactData.ADD) return;
                if (c.normalForce < forceThreshold) return;
                //System.out.println(c.state);
				Actor a1 = (Actor)c.shape1.getBody().getUserData();
				Actor a2 = (Actor)c.shape2.getBody().getUserData();
				float vol = 1.0f*(c.normalForce-forceThreshold)/(maxForce-forceThreshold);
				if (vol > 1.0f) vol = 1.0f;
				float pan = getPan(c.position);
				if (a1.isInGroup(BULLET_GROUP) && a2.isInGroup(BULLET_GROUP)) {
					if (c.normalForce > forceThreshold) {
						bulletBounceSound(vol,pan);
					}
				} else if (a1.isInGroup(PLAYER_GROUP) || a2.isInGroup(PLAYER_GROUP)) {
					if (c.normalForce > forceThreshold) {
						bulletPlayerSound(vol,pan);
					}
				} else if //(a1.getGroupFlags()!=0 && a2.getGroupFlags()!=0){
					//if 
                          (c.normalForce > forceThreshold*10) {
						bulletThudSound(vol,pan);
					//}
				}
				//System.out.println(a1.getName() + " collided with " + a2.getName() + " at "+c.position);
			}
			
		};
		
		/*
		BlobStructure structure = new SquareLatticeStructure();
		structure.setSpringFrequency(30.0f);
		structure.setSpringDamping(3.7f);
		BlobMaker.pointRadius = 0.11f;
		BlobMaker.pointDensity = 3.0f;
		
		BlobContainer container = new DonutBlobContainer(m_field.getCenter().add(new Vec2(0.0f,6.0f)),0.5f,1.6f);
		BlobMaker.createBlob(structure, container, world,
				0.24f,      0.24f,     0.1f,  0.1f);
		container = new DonutBlobContainer(m_field.getCenter(),0.5f,1.6f);
		
		BlobMaker.createBlob(structure, container, world,
				0.24f,      0.24f,     0.1f,  0.1f);
		//container = new DonutBlobContainer(m_field.getCenter().sub(new Vec2(0.0f,6.0f)),0.5f,1.6f);
		//BlobMaker.createBlob(structure, container, world,
		//		0.2f,      0.2f,     0.1f,  0.1f);
		*/
		
		
		// Create pucks
		m.setDensity(10.0f);
		
		pucks = new Body[puckNumber];
		float fieldHeight = field.getHeight();
		float puckX = field.getCenter().x;
		float diff = fieldHeight / (puckNumber + 1);
		float puckY = field.getCenter().y - fieldHeight*.5f + diff;
		for (int i=0; i<puckNumber; ++i) {
			//pucks[i] = createPuck(puckX, puckY, puckSize);
			puckY += diff;
		}
		

        puckBitWidth = 4.0f/6.0f;
        puckBitHeight = 3.0f/6.0f;
        puckBits = new ArrayList<Body>();
        /*
        ArrayList<Body> listA = PuckMaker.createSquareBlob(field.getCenter().x - 2.0f, field.getCenter().y - 5.0f,
                                   field.getCenter().x + 2.0f, field.getCenter().y - 2.0f,
                                   6, 6, world);
        ArrayList<Body> listB = PuckMaker.createSquareBlob(field.getCenter().x-2.0f, field.getCenter().y-1.5f,
								   field.getCenter().x+2.0f, field.getCenter().y+1.5f,
								   6, 6, world);
        ArrayList<Body> listC = PuckMaker.createSquareBlob(field.getCenter().x-2.0f, field.getCenter().y+2.0f,
                                   field.getCenter().x+2.0f, field.getCenter().y+5.0f,
                                   6, 6, world);

        puckBits.addAll(listA);
        puckBits.addAll(listB);
        puckBits.addAll(listC);   */

        totalPuckMass = 0.0f;
        //for (Body bod:puckBits) {
        //    totalPuckMass += bod.getMass();
        //}

        targetPuckMass = 100f;//totalPuckMass;

        fillMissingSprites();
		
		// Fill in missing shape data
		world.generateValidShapeData();
		
		// Set up contact listener for bullet events
		world.getContactEventDispatcher().registerGroupListener(BULLET_GROUP, bulletListener);
		
		// Initialize particle emission
		images = CoreImage.load("particles2.png").split(6, 1);
        particleLayer = new Group();
        // Particles should ignore mouse input
        particleLayer.enabled.set(false);
        // Particles look good with additive blending
        particleLayer.setBlendMode(BlendMode.Add());
        addLayer(particleLayer);
		
	}

    private void fillMissingSprites() {
        //if (true) return;
        // Draw black sprites for any shapes missing them
		Body bList = m_world.getBodyList();
		Java2DBodySprite.setFill(Color.BLACK);
		Java2DBodySprite.setStroke(Color.BLACK);
		while (bList != null) {
			if (physics.getSpriteByBody(bList) == null && bList.getShapeList() != null) {
				Java2DBodySprite bSprite = new Java2DBodySprite(bList,physics,0.11f);
                physics.add(bSprite);
				bList.m_angularDamping = 0.3f;
				bList.m_linearDamping = 0.8f;
				bList.setBullet(false);
				gravityFreeList.add(bList);
//				System.out.println("Made one");
			}
			bList = bList.getNext();
		}
    }

    private void checkPuckBits() {
        addMissingPuckBits();
        
        ArrayList<Body> toRemove = null;

        // Create a list of bodies to be removed
        for (Body b:puckBits) {
            if (b.getWorldCenter().x < m_field.getLeftSemiCenter().x) {
                if (toRemove == null) toRemove = new ArrayList<Body>();
                puckBitHitGoal(b,FieldSide.LEFT);
                toRemove.add(b);
            } else if (b.getWorldCenter().x > m_field.getRightSemiCenter().x) {
                if (toRemove == null) toRemove = new ArrayList<Body>();
                puckBitHitGoal(b,FieldSide.RIGHT);
                toRemove.add(b);
            }
        }

        if (toRemove == null) return;

        // Actually remove the bodies
        for (Body b:toRemove) {
            makeParticlesWorld(b.getWorldCenter(),b.getWorldCenter(),40,2.0f);
            puckBits.remove(b);
            physics.removeSpritesByBody(b);
            m_world.destroyBody(b);
        }
    }

    float puckTimer = 0.0f;
    float totalTimer = 0.0f;
    private void addMissingPuckBits() {
        puckTimer += 0.1f;
        totalTimer += 0.1f;
        //System.out.println(totalTimer);
        float proportionDiff = (targetPuckMass - totalPuckMass) / targetPuckMass;
        if (proportionDiff < puckMassTolerance) return;
        if (puckTimer < 4.0f) return;
        float targetX = m_field.getCenter().x;
        float targetY = m_field.getCenter().y;
        targetX += (float)Math.sin(totalTimer)*2.0f;
        targetY += .4f*m_field.getHeight()*(float)Math.cos(totalTimer);
        float newWidth = puckBitWidth * 3;
        float newHeight = puckBitHeight * 3;
        ArrayList<Body> list =
                PuckMaker.createSquareBlob(
                        targetX - .5f*newWidth, targetY - .5f*newHeight,
                        targetX + .5f*newWidth, targetY + .5f*newHeight,
                        3, 3, m_world);
        puckBits.addAll(list);

        puckTimer = 0.0f;
        for (Body b:list) {
            totalPuckMass += b.getMass();
        }

        fillMissingSprites();
        m_world.generateValidShapeData();

    }

    float totalPuckMass;
    float targetPuckMass;
    final float puckMassTolerance = 0.0f;

    private void puckBitHitGoal(Body b, FieldSide side) {
        if (enemyAI.targetBody == b) enemyAI.targetBody = null;
        float toSub = b.getMass();
        totalPuckMass -= toSub;
        switch(side){
            case LEFT:
                m_field.subtractFromLifeL(toSub);
                puckBitCrossSound(true);
                break;
            case RIGHT:
                m_field.subtractFromLifeR(toSub);
                puckBitCrossSound(false);
                break;
        }
    }

    private Body createPuck(float x, float y, float size) {
		return PuckMaker.createPuck(x, y, size, physics.getWorld());
	}
	
	int ammoLLast = 0;
	int ammoRLast = 0;
    int lifeLLast = 0;
    int lifeRLast = 0;

    @Override
	public void update(int elapsedTime) {
        System.out.println("Count: "+PuckMaker.count);
                
        PuckMaker.count = 0;

        super.update(elapsedTime);
        /*
        float lx = m_field.getLeftSemiCenter().x;
        float rx = m_field.getRightSemiCenter().x;
        float y0 = m_field.getCenter().y-m_field.getHeight()*.5f;
        float y1 = y0 + m_field.getHeight();
        float rand = (float)Math.random();
        float y = rand*y0 + (1.0f-rand)*y1;
        y0 = (1.0f-rand)*y0 + rand*y1;
        makeParticlesWorld(new Vec2(lx,y0),new Vec2(lx,y0),1,1.0f);
        makeParticlesWorld(new Vec2(rx,y),new Vec2(rx,y),1,1.0f);
        */

        enemyInput.storeLast();
        
        enemyAI.updateEnemyInput();

        handleParticles();
		
		playedClickThisFrame = false;	
		
		setBullets();


		handleKeys();
		
		stabilizeDudes();
		
		pinMouse();
		
		tetherGun(dollL, true);
		tetherGun(dollR, false);
		
		applyBulletForces();
		
		float gy = physics.getWorld().getGravity().y;
		for (Body b:gravityFreeList) {
			b.applyForce(new Vec2(0.0f,-b.getMass()*gy), b.getWorldCenter());
		}
		
		checkPuckBits();

        checkScore();

        if (ammoLLast != m_field.getAmmoL()) {
			ammoLLabel.setText("Ammo: "+m_field.getAmmoL());
			Timeline timeline = new Timeline();
			Label label = ammoLLabel;
            timeline.at(0).animate(label.angle, 0.05, 0, 300, Easing.ELASTIC_OUT);
            addTimeline(timeline);
		}
		
		if (ammoRLast != m_field.getAmmoR()) {
			ammoRLabel.setText("Ammo: "+m_field.getAmmoR());
			Timeline timeline = new Timeline();
			Label label = ammoRLabel;
            timeline.at(0).animate(label.angle, -0.05, 0, 300, Easing.ELASTIC_OUT);
            addTimeline(timeline);
		}

        if (lifeLLast != (int)m_field.getLifeL()) {
            lifeLLabel.setText("Score: "+(int)m_field.getLifeL());
			Timeline timeline = new Timeline();
			Label label = lifeLLabel;
            timeline.at(0).animate(label.height, 1.5*lifeLabelDefaultHeight, lifeLabelDefaultHeight, 500, Easing.ELASTIC_OUT);
            addTimeline(timeline);
        }

        if (lifeRLast != (int)m_field.getLifeR()) {
            lifeRLabel.setText("Score: "+(int)m_field.getLifeR());
			Timeline timeline = new Timeline();
			Label label = lifeRLabel;
            timeline.at(0).animate(label.height, 1.5*lifeLabelDefaultHeight, lifeLabelDefaultHeight, 500, Easing.ELASTIC_OUT);
            addTimeline(timeline);
        }

        ammoLLast = m_field.getAmmoL();
		ammoRLast = m_field.getAmmoR();
        lifeLLast = (int)m_field.getLifeL();
        lifeRLast = (int)m_field.getLifeR();



    }

    //float nextThreshold = 100.0f;

    private void checkScore() {
        //float totalScore = - (m_field.getLifeL() + m_field.getLifeR());
        //if (totalScore > nextThreshold) {
        //    CoreImage screenShot = Stage.getScreenshot();
        //    Stage.pushScene(new ScoreScene(m_field.getLifeL(), m_field.getLifeR(), screenShot));
        //    nextThreshold += 100.0f;
        //}
    }

    private void setBullets() {
        Body bList = physics.getWorld().getBodyList();
		float bulletVelSqr = 1000f;
        int bullets = 0;
        int nonBullets = 0;
        while (bList != null) {
			Vec2 v = bList.getLinearVelocity();
			if (v.lengthSquared() >= bulletVelSqr) {
                ++bullets;
                bList.setBullet(true);
            }
			else {
                ++nonBullets;
                bList.setBullet(false);
            }

            bList = bList.getNext();
		}
        
    }
	
	private void applyBulletForces() {
		Body b = physics.getWorld().getBodyList();
		while (b != null) {
			Actor a = (Actor)b.getUserData();
			if (a.isInGroup(BULLET_GROUP)) m_field.applyForceField(b);
			b = b.getNext();
		}
	}
	
	
	private void handleKeys() {
		
		if (Input.isDown(Input.KEY_W)) { //up
			jetPackL.applyForce(new Vec2(0.0f,jetPackForceU), jetPackL.getWorldCenter());
			jetPackL.applyTorque(100.0f);
		}

		if (Input.isDown(Input.KEY_S)) { //down
			jetPackL.applyForce(new Vec2(0.0f,-jetPackForceD), jetPackL.getWorldCenter());
			jetPackL.applyTorque(-100.0f);
		}
		
		if (Input.isDown(Input.KEY_A)) { //left
			jetPackL.applyForce(new Vec2(-jetPackForceLR,0.0f), jetPackL.getWorldCenter());
		}
		
		if (Input.isDown(Input.KEY_D)) { //right
			jetPackL.applyForce(new Vec2(jetPackForceLR,0.0f), jetPackL.getWorldCenter());
		}

        boolean[] udlr = enemyInput.getArrows();
        if (udlr[0]) { //up
			jetPackR.applyForce(new Vec2(0.0f,jetPackForceU), jetPackR.getWorldCenter());
			jetPackR.applyTorque(-100.0f);
		}

		if (udlr[1]) { //down
			jetPackR.applyForce(new Vec2(0.0f,-jetPackForceD), jetPackR.getWorldCenter());
			jetPackR.applyTorque(100.0f);
		}

		if (udlr[2]) { //left
			jetPackR.applyForce(new Vec2(-jetPackForceLR,0.0f), jetPackR.getWorldCenter());
		}

		if (udlr[3]) { //right
			jetPackR.applyForce(new Vec2(jetPackForceLR,0.0f), jetPackR.getWorldCenter());
		}

//        if (Input.isPressed(Input.KEY_K)) {
//            shortenPlayerLeash();
//        }
//        if (Input.isPressed(Input.KEY_L)) {
//            shortenEnemyLeash();
//        }

        if (Input.isPressed(Input.KEY_SPACE)) {
            sprayBullets(true);
        }

        if (Input.isPressed(Input.KEY_M)) {
            playSounds = !playSounds;
        }
    }

    private void shortenEnemyLeash() {
        // Connect player tether to field
        physics.removeSpritesByBody(tubeR.get(0));
        m_world.destroyBody(tubeR.get(0));
        tubeR.remove(0);
        RevoluteJointDef rjd = new RevoluteJointDef();
		rjd.initialize(m_field.getBody(),tubeR.get(0),m_field.getRightEdge());
        rjd.localAnchor2 = localTubeOffset;//.mul(-1.0f);
        m_world.createJoint(rjd);
    }

    private void shortenPlayerLeash() {
        // Connect player tether to field
        physics.removeSpritesByBody(tubeL.get(0));
        m_world.destroyBody(tubeL.get(0));
        tubeL.remove(0);
        RevoluteJointDef rjd = new RevoluteJointDef();
		rjd.initialize(m_field.getBody(),tubeL.get(0),m_field.getLeftEdge());
        rjd.localAnchor2 = localTubeOffset;
        m_world.createJoint(rjd);
    }
	

    private void handleMouseShot() {
		boolean madeStuff = false;
		if (Input.isMousePressed()) {
			if (m_field.getAmmoL() <= 0) {
				clickSound();
				return;
			}
		}
		if (Input.isMouseDown() && lMouseTimer >= 1.0f) {
			BodySprite bs = physics.getSpriteByBody(gunL);
			
			if (m_field.getAmmoL() <= 0) {
				return;
			}
			leftShotPower *= 1.02f;
			if (leftShotPower > maxShotPower) leftShotPower = maxShotPower;
			int nParts = (int)(leftShotPower * 3);
			float baseSize = 1.0f;
			Vec2 tipVecA = gunL.getWorldCenter().add(gunL.getWorldVector(new Vec2(1.0f,0.0f)));
			Vec2 canvasTipVecA = physics.getCanvasVector(tipVecA);
			Vec2 tipVecB = gunL.getWorldCenter().add(gunL.getWorldVector(new Vec2(leftShotPower,0.0f)));
			Vec2 canvasTipVecB = physics.getCanvasVector(tipVecB);
			//System.out.println(canvasTipVec);
			makeParticles((int)canvasTipVecA.x,(int)canvasTipVecA.y,(int)canvasTipVecB.x,(int)canvasTipVecB.y,nParts,leftShotPower*baseSize);

		}
		if (Input.isMouseReleased() && lMouseTimer >= 1.0f) {

			lMouseTimer = 0.0f;
			if (m_field.getAmmoL() <= 0) {
				clickSound();
				return;
			}
			m_field.decrementAmmoL();
			float leftShotPowerSound = .5f+.5f*(leftShotPower - 1.0f) / (1.5f);
			float rad = Math.min(0.15f*leftShotPower, 0.15f*1.7f);
			shootBullet(gunL,
					gunL.getWorldCenter().add(gunL.getWorldVector(new Vec2(1.0f,0.0f))),
					gunL.getWorldVector(new Vec2(shootSpeed*leftShotPower,0.0f)),
					1.0f*leftShotPower, rad,leftShotPowerSound);
			leftShotPower = 1.0f;
			Vec2 tipVecA = gunL.getWorldCenter().add(gunL.getWorldVector(new Vec2(1.0f,0.0f)));
			Vec2 canvasTipVecA = physics.getCanvasVector(tipVecA);
			Vec2 tipVecB = gunL.getWorldCenter().add(gunL.getWorldVector(new Vec2(2*leftShotPower,0.0f)));
			Vec2 canvasTipVecB = physics.getCanvasVector(tipVecB);
			//System.out.println(canvasTipVec);
			int nParts = (int)(leftShotPower * 40);
			float baseSize = leftShotPower;
			makeParticles((int)canvasTipVecA.x,(int)canvasTipVecA.y,(int)canvasTipVecB.x,(int)canvasTipVecB.y,nParts,leftShotPower*baseSize);
			madeStuff = true;
		} else {
			if (!Input.isMouseDown()) leftShotPower = 1.0f;
			lMouseTimer += 1.0f/reloadFrames;
		}
		if (madeStuff) physics.getWorld().generateValidShapeData();
	}

    private void sprayBullets(boolean isLeft) {
        float leftShotPower2 = 2.0f;
        float n = 0;
        for (int i=0; i<5; ++i) {
            ++n;
            if (m_field.getAmmoL() <= 0) {
		    		clickSound();
		    		break;
	        }
            m_field.decrementAmmoL();
            float r = 0.1f*(float)(Math.random()-.5);
            float s = 0.1f*(float)(Math.random()-.5);

            shootBullet(gunL,
					gunL.getWorldCenter().add(gunL.getWorldVector(new Vec2(1.0f+s,0.0f+r))),
					gunL.getWorldVector(new Vec2(shootSpeed*leftShotPower2+s,r)),
					1.0f*leftShotPower, .2f,1.0f);

            physics.getWorld().generateValidShapeData();
        }
        leftShotPower2 *= n/5.0f;
        Vec2 tipVecA = gunL.getWorldCenter().add(gunL.getWorldVector(new Vec2(1.0f,0.0f)));
        Vec2 canvasTipVecA = physics.getCanvasVector(tipVecA);
        Vec2 tipVecB = gunL.getWorldCenter().add(gunL.getWorldVector(new Vec2(2*leftShotPower2,0.0f)));
        Vec2 canvasTipVecB = physics.getCanvasVector(tipVecB);

        int nParts = (int)(leftShotPower2 * 70);
        float baseSize = leftShotPower2;
        makeParticles((int)canvasTipVecA.x,(int)canvasTipVecA.y,(int)canvasTipVecB.x,(int)canvasTipVecB.y,nParts,leftShotPower2*baseSize);

    }

    public void makeParticlesWorld(Vec2 source, Vec2 target, int nParts, float size) {
		Vec2 canvasTipVecA = physics.getCanvasVector(source);
		Vec2 canvasTipVecB = physics.getCanvasVector(target);
		makeParticles((int)canvasTipVecA.x,(int)canvasTipVecA.y,(int)canvasTipVecB.x,(int)canvasTipVecB.y,nParts,size);
	}
	
	
	private void stabilizeDudes() {
		float angle = jetPackL.getAngle();
		float sign = 2.0f*jetPackTargetAngle-angle;//(angle > jetPackTargetAngle)?-1.0f:1.0f;
		float torque = jetPackAngularSpringStrength*sign;
		jetPackL.applyTorque(torque);
		angle = jetPackR.getAngle();
		sign = 2.0f*jetPackTargetAngle-angle;//(angle > jetPackTargetAngle)?-1.0f:1.0f;
		torque = jetPackAngularSpringStrength*sign;
		jetPackR.applyTorque(torque);
		poseLegs(bipedL);
		poseLegs(bipedR);
	}
	
	private void pinMouse() {
		/*
		 * Target the mouseJoint to the physics-coordinates of the current mouse location.
		 */
		if (physics != null)
		{
			double pX = physics.getPhysicsX(
				sm.x.get(), sm.y.get());
			double pY = physics.getPhysicsY(
				sm.x.get(), sm.y.get());
			double eX = getEnemyTarget().x;
			double eY = getEnemyTarget().y;
			if (true || pX > -physics.getPhysicsWidth() / 2
					&& pY > -physics.getPhysicsHeight() / 2)
			{

				if (playerMouseJoint != null)
				{
					float dx = (float)pX - gunL.getWorldCenter().x;
					float dy = (float)pY - gunL.getWorldCenter().y;
					Vec2 diff = new Vec2(dx,dy);
					float desiredL = 2.0f;
					if (diff.lengthSquared() < desiredL*desiredL) {
						if (diff.x < Settings.EPSILON && diff.y < Settings.EPSILON) {
							
						} else {
							diff.normalize();
							diff.mulLocal(desiredL);
							pX = gunL.getWorldCenter().x + diff.x;
							pY = gunL.getWorldCenter().y + diff.y;
						}
					}
					if (pX < gunL.getWorldCenter().x + 3.0f) {
						pX = gunL.getWorldCenter().x + 3.0f;
					}
					playerMouseJoint.m_target.set(
						(float) pX, (float) pY);
					//mj2.m_target.set(
					//	(float) (2*gun.getWorldCenter().x-pX),
					//	(float) (2*gun.getWorldCenter().y-pY));
					handleMouseShot();
				}
				if (enemyMouseJoint != null)
				{
					float dx = (float)eX - gunR.getWorldCenter().x;
					float dy = (float)eY - gunR.getWorldCenter().y;
					Vec2 diff = new Vec2(dx,dy);
					float desiredL = 2.0f;
					if (diff.lengthSquared() < desiredL*desiredL) {
						if (diff.x < Settings.EPSILON && diff.y < Settings.EPSILON) {
							
						} else {
							diff.normalize();
							diff.mulLocal(desiredL);
							eX = gunR.getWorldCenter().x + diff.x;
							eY = gunR.getWorldCenter().y + diff.y;
						}
					}
					if (eX > gunR.getWorldCenter().x - 3.0f) {
						eX = gunR.getWorldCenter().x - 3.0f;
					}
					enemyMouseJoint.m_target.set(
						(float) eX, (float) eY);
					//mj2.m_target.set(
					//	(float) (2*gun.getWorldCenter().x-pX),
					//	(float) (2*gun.getWorldCenter().y-pY));
					handleEnemyShot();
				}
			}
		}
	}


    private void handleEnemyShot() {
		boolean madeStuff = false;
		if (enemyInput.isMousePressed()) {
			if (m_field.getAmmoR() <= 0) {
				clickSound();
				return;
			}
		}
		if (enemyInput.isMouseDown() && rMouseTimer >= 1.0f) {
			BodySprite bs = physics.getSpriteByBody(gunR);

			if (m_field.getAmmoR() <= 0) {
				return;
			}
			rightShotPower *= 1.02f;
			if (rightShotPower > maxShotPower) rightShotPower = maxShotPower;
			int nParts = (int)(rightShotPower * 3);
			float baseSize = 1.0f;
			Vec2 tipVecA = gunR.getWorldCenter().add(gunR.getWorldVector(new Vec2(-1.0f,0.0f)));
			Vec2 canvasTipVecA = physics.getCanvasVector(tipVecA);
			Vec2 tipVecB = gunR.getWorldCenter().add(gunR.getWorldVector(new Vec2(-rightShotPower,0.0f)));
			Vec2 canvasTipVecB = physics.getCanvasVector(tipVecB);
			//System.out.println(canvasTipVec);
			makeParticles((int)canvasTipVecA.x,(int)canvasTipVecA.y,(int)canvasTipVecB.x,(int)canvasTipVecB.y,nParts,rightShotPower*baseSize);

		}
		if (enemyInput.isMouseReleased() && rMouseTimer >= 1.0f) {
			rMouseTimer = 0.0f;
			if (m_field.getAmmoR() <= 0) {
				clickSound();
				return;
			}
			m_field.decrementAmmoR();
			float rightShotPowerSound = .5f+.5f*(rightShotPower - 1.0f) / (1.5f);
			float rad = Math.min(0.15f*rightShotPower, 0.15f*1.7f);
			shootBullet(gunR,
					gunR.getWorldCenter().add(gunR.getWorldVector(new Vec2(-1.0f,0.0f))),
					gunR.getWorldVector(new Vec2(-shootSpeed*rightShotPower,0.0f)),
					1.0f*rightShotPower, rad,rightShotPowerSound);
			rightShotPower = 1.0f;
			Vec2 tipVecA = gunR.getWorldCenter().add(gunR.getWorldVector(new Vec2(-1.0f,0.0f)));
			Vec2 canvasTipVecA = physics.getCanvasVector(tipVecA);
			Vec2 tipVecB = gunR.getWorldCenter().add(gunR.getWorldVector(new Vec2(-2*rightShotPower,0.0f)));
			Vec2 canvasTipVecB = physics.getCanvasVector(tipVecB);
			//System.out.println(canvasTipVec);
			int nParts = (int)(rightShotPower * 40);
			float baseSize = rightShotPower;
			makeParticles((int)canvasTipVecA.x,(int)canvasTipVecA.y,(int)canvasTipVecB.x,(int)canvasTipVecB.y,nParts,rightShotPower*baseSize);
			madeStuff = true;
		} else {
			if (!enemyInput.isMouseDown()) rightShotPower = 1.0f;
			rMouseTimer += 1.0f/reloadFrames;
		}
		if (madeStuff) physics.getWorld().generateValidShapeData();
	}
	
	private Vec2 getEnemyTarget() {
        double pX = enemyInput.getMouseX();
		double pY = enemyInput.getMouseY();
		return new Vec2((float)pX, (float)pY);
	}
	
	private void tetherGun(PlayerDoll doll, boolean isLeft) {
		Biped biped = doll.biped;
		float lrSign = isLeft?1.0f:-1.0f;
		Body gun = isLeft?gunL:gunR;
		if (doll.isGunUntethered) {
			addSpringForce(biped.RHand,
					biped.RHand.getLocalCenter(),
					gun,
					gun.getLocalCenter().add(new Vec2(lrSign*0.1f,-0.13f)),
					1020.0f,6.8f,0.0f);
			addSpringForce(biped.LHand,
					biped.LHand.getLocalCenter(),
					gun,
					gun.getLocalCenter().sub(new Vec2(lrSign*0.8f,0.2f)),
					1020.0f,6.8f,0.0f);
			Vec2 gunRH = gun.getWorldCenter().add(gun.getWorldVector(new Vec2(lrSign*0.1f,-0.13f)));
			Vec2 gunLH = gun.getWorldCenter().add(gun.getWorldVector(new Vec2(-0.8f*lrSign,-0.2f)));
			
			Vec2 diffL = biped.LHand.getWorldCenter().sub(gunLH);
			Vec2 diffR = biped.RHand.getWorldCenter().sub(gunRH);
			float ldistsqr = diffL.lengthSquared();
			float rdistsqr = diffR.lengthSquared();
			if (ldistsqr < 0.01f && rdistsqr < 0.01f) {
				doll.isGunUntethered = false;
				RevoluteJointDef rjd = new RevoluteJointDef();
				rjd.initialize(biped.LHand, gun, biped.LHand.getWorldCenter());
                physics.getWorld().createJoint(rjd);
				rjd.initialize(biped.RHand, gun, biped.RHand.getWorldCenter());
				physics.getWorld().createJoint(rjd);
				turnOnAiming(isLeft);
			}
		} else {
			addSpringForce(biped.Chest,
				biped.Chest.getLocalCenter(),
				gun,
				gun.getLocalCenter().sub(new Vec2(lrSign*0.9f,0.0f)),
				420.0f,6.8f,0.0f);
		}
	}
	
	private void turnOnAiming(boolean isLeft) {
		Body gun = null;
		float lrSign = 1.0f;
		if (!isLeft){
			gun = gunR;
			lrSign = -1.0f;
		}
		else gun = gunL;
		float gunCX = gun.getWorldCenter().x;
		float gunCY = gun.getWorldCenter().y;
		NewWorld world = physics.getWorld();
		Vec2 targetLoc = gun.getWorldCenter().add(gun.getWorldVector(new Vec2(lrSign*1.0f,0.0f)));
		Body b = BodyUtils.createCircle(world, targetLoc.x, targetLoc.y, 0.25f);
		Material m = Material.PLASTIC();
		m.setDensity(50.0f);
		m.applyAll(b);
		BodySprite bodySprite2 = new Java2DBodySprite(b,physics,3.0f);
		physics.add(bodySprite2);
		b.getShapeList().m_isSensor = true;
		PrismaticJointDef pjd = new PrismaticJointDef();
		pjd.initialize(gun, b, b.getWorldCenter(), gun.getWorldVector(new Vec2(lrSign*1.0f,0.0f)));
		pjd.enableLimit = true;
        pjd.upperTranslation = 1000f;
        pjd.lowerTranslation = 1.0f;
        world.createJoint(pjd);
		
		b.allowSleeping(false);
		MouseJointDef md = new MouseJointDef();
		md.body1 = world.getGroundBody();
		md.body2 = b;
		md.maxForce = 250.0f * b.getMass() * 10.0f;
		md.dampingRatio = 0.5f;
		md.frequencyHz = 4.5f;
		if (isLeft) {
			playerMouseJoint = (MouseJoint) world.createJoint(md);
			playerMouseJoint.m_localAnchor = new Vec2(0, 0);
			playerMouseJoint.setTarget(b.getPosition());
		} else {
			enemyMouseJoint = (MouseJoint) world.createJoint(md);
			enemyMouseJoint.m_localAnchor = new Vec2(0, 0);
			enemyMouseJoint.setTarget(b.getPosition());
		}
		/*
		b = BodyUtils.createCircle(world, gunCX-1.0f, gunCY, 0.25f);
		m.setDensity(50.0f);
		m.applyAll(b);
		BodySprite bodySprite3 = new Java2DBodySprite(b,physics,3.0f);
		physics.add(bodySprite3);
		*/
		world.generateValidShapeData();
	}
	
	
	private void shootBullet(Body recoilBody, Vec2 position, Vec2 velocity, float mass, float radius, float soundLevel) {
		Java2DBodySprite.setFill(Color.white);
		NewWorld world = physics.getWorld();
		Body b = BodyUtils.createCircle(world, position.x, position.y, radius);
		Material m = new Material();
		float density = mass / ((float)Math.PI * radius * radius);
		m.setDensity(density);
		m.setRestitution(0.7f);
		b.setBullet(true);
		BodySprite bodySprite = new Java2DBodySprite(b,physics,1.0f);
		physics.add(bodySprite);
		b.setUserData(new Actor());
		((Actor)b.getUserData()).addToGroup(BULLET_GROUP);
		m.applyAll(b);
		Vec2 impulse = new Vec2(velocity.x * mass, velocity.y * mass);
		b.applyImpulse(impulse, b.getWorldCenter());
		impulse.mulLocal(-recoilFactor);
		recoilBody.applyImpulse(impulse, recoilBody.getWorldCenter());
		cannonSound(soundLevel,getPan(recoilBody.getWorldCenter()));
	}
	

	public void addSpringForce(Body bA, Vec2 localA, Body bB, Vec2 localB, float k, float friction, float desiredDist) {
        Vec2 pA = bA.getWorldPoint(localA);
        Vec2 pB = bB.getWorldPoint(localB);
        Vec2 diff = pB.sub(pA);
        //Find velocities of attach points
        Vec2 vA = bA.m_linearVelocity.sub(Vec2.cross(bA.getWorldVector(localA), bA.m_angularVelocity));
        Vec2 vB = bB.m_linearVelocity.sub(Vec2.cross(bB.getWorldVector(localB), bB.m_angularVelocity));
        Vec2 vdiff = vB.sub(vA);
        float dx = diff.normalize(); //normalizes diff and puts length into dx
        //System.out.println(dx);
        float vrel = vdiff.x*diff.x + vdiff.y*diff.y;
        float forceMag = -k*(dx-desiredDist) - friction*vrel;
        diff.mulLocal(forceMag);
        bB.applyForce(diff, bA.getWorldPoint(localA));
        bA.applyForce(diff.mulLocal(-1f), bB.getWorldPoint(localB));
        bA.wakeUp();
        bB.wakeUp();
    }
	

	private void poseLegs(Biped biped) {
		float lrSign = 1.0f;
		if (biped == bipedR) lrSign = -1.0f;
		float lAngleHip = biped.LHip.getJointAngle();
		float rAngleHip = biped.RHip.getJointAngle();
		//float lAngleHipSpeed = biped.LHip.getJointSpeed();
		//float rAngleHipSpeed = biped.RHip.getJointSpeed();
		//System.out.println(lAngleHip+" "+rAngleHip);
		biped.LHip.enableMotor(true);
		biped.RHip.enableMotor(true);
		float lSpeed = hipConstant*(lAngleHipTarget*lrSign - lAngleHip);
		float rSpeed = hipConstant*(rAngleHipTarget*lrSign - rAngleHip);
		float lTorque = 20.0f;
		float rTorque = lTorque;
		if (Math.abs(lAngleHipTarget*lrSign-lAngleHip)<angleThreshold) lTorque = 0.1f;
		if (Math.abs(rAngleHipTarget*lrSign-rAngleHip)<angleThreshold) rTorque = 0.1f;
		
		biped.LHip.setMotorSpeed(lSpeed);
		biped.RHip.setMotorSpeed(rSpeed);
		biped.LHip.setMaxMotorTorque(lTorque);
		biped.RHip.setMaxMotorTorque(rTorque);
		
		float lAngleKnee = biped.LKnee.getJointAngle();
		float rAngleKnee = biped.RKnee.getJointAngle();
		//float lAngleHipSpeed = biped.LHip.getJointSpeed();
		//float rAngleHipSpeed = biped.RHip.getJointSpeed();
		//System.out.println(lAngleHip+" "+rAngleHip);
		biped.LKnee.enableMotor(true);
		biped.RKnee.enableMotor(true);
		float lkSpeed = kneeConstant*(lAngleKneeTarget*lrSign - lAngleKnee);
		float rkSpeed = kneeConstant*(rAngleKneeTarget*lrSign - rAngleKnee);
		float lkTorque = 25.0f;
		float rkTorque = lkTorque;
		if (Math.abs(lAngleKneeTarget*lrSign-lAngleKnee)<angleThreshold) lkTorque = 0.1f;
		if (Math.abs(rAngleKneeTarget*lrSign-rAngleKnee)<angleThreshold) rkTorque = 0.1f;
		
		biped.LKnee.setMotorSpeed(lkSpeed);
		biped.RKnee.setMotorSpeed(rkSpeed);
		biped.LKnee.setMaxMotorTorque(lkTorque);
		biped.RKnee.setMaxMotorTorque(rkTorque);
	}


    long lastPlayedBounceAt = -1000;

    private void bulletBounceSound(float vol, float pan) {
        if (!playSounds) return;
        if (System.currentTimeMillis()-lastPlayedBounceAt < 100L) return;
		int randomInt = (int)Math.floor(Math.random()*(tickSounds.length-.001));
		tickSounds[randomInt].play(new Fixed(vol),new Fixed(pan));
        lastPlayedBounceAt = System.currentTimeMillis();
    }
	
	private void bulletThudSound(float vol, float pan) {
        if (!playSounds) return;
		//if (playedClickThisFrame) {
		//	System.out.println("skip");
		//	return;
		//}
		//playedClickThisFrame = true;
		//int randomInt = (int)Math.floor(Math.random()*(thudSounds.length-.001));
		//thudSounds[randomInt].play(new Fixed(vol),new Fixed(pan));
	}
	
	private void bulletPlayerSound(float vol, float pan) {
        if (!playSounds) return;
		//int randomInt = (int)Math.floor(Math.random()*(thudSounds.length-.001));
		//thudSounds[randomInt].play(new Fixed(vol),new Fixed(pan));
		//randomInt = (int)Math.floor(Math.random()*(oofSounds.length-.001));
		//oofSounds[randomInt].play(new Fixed(vol),new Fixed(pan));
	}

    long lastPlayedCannonAt = -1000;
    private void cannonSound(float vol, float pan) {
        if (!playSounds) return;
        if (System.currentTimeMillis()-lastPlayedCannonAt < 100L) return;
        int randomInt = (int)Math.floor(Math.random()*(gunSounds.length-.001));
		gunSounds[randomInt].play(new Fixed(vol),new Fixed(pan));
        lastPlayedCannonAt = System.currentTimeMillis();
    }
	
	public void slurpSound(float vol, float pan) {
        if (!playSounds) return;
		slurpSound.play(new Fixed(vol),new Fixed(pan));
	}
	
	private void clickSound() {
        if (!playSounds) return;
		bulletBounceSound(0.4f,0.0f);
	}

    long lastPlayedCrossAt = -1000;

    private void puckBitCrossSound(boolean playerSide) {
        if (!playSounds) return;
        if (System.currentTimeMillis()-lastPlayedCrossAt < 100L) return;
        if (playerSide) {
            losePoint.play(new Fixed(1.0f), new Fixed(-1.0f));
        } else {
            winPoint.play(new Fixed(1.0f), new Fixed(1.0f));
        }

        lastPlayedCrossAt = System.currentTimeMillis();
    }

    public float getPan(Vec2 worldCoord) {
		float xMin = m_field.getLeftEdge().x;
		float xMax = m_field.getRightEdge().x;
		float x = worldCoord.x;
		return (x-xMin)/(xMax-xMin);
	}
	
}
